Skip to content

[compiler] Rewrite React Compiler Docs #7868

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open

[compiler] Rewrite React Compiler Docs #7868

wants to merge 1 commit into from

Conversation

poteto
Copy link
Member

@poteto poteto commented Jul 11, 2025

We've received feedback that the compiler docs are difficult to understand and not prominent enough that people don't realize the compiler is a serious project and is near stable.

This PR rewrites the whole compiler doc section, giving it its own category as well as a standalone reference page.

Preview: https://react-dev-git-pr7868-fbopensource.vercel.app/

Copy link

github-actions bot commented Jul 11, 2025

Size changes

📦 Next.js Bundle Analysis for react-dev

This analysis was generated by the Next.js Bundle Analysis action. 🤖

Five Pages Changed Size

The following pages changed size from the code in this PR compared to its base branch:

Page Size (compressed) First Load
/404 127.91 KB (🟡 +59 B) 238.35 KB
/500 127.92 KB (🟡 +59 B) 238.36 KB
/[[...markdownPath]] 129.97 KB (🟡 +199 B) 240.42 KB
/errors 128.16 KB (🟡 +59 B) 238.61 KB
/errors/[errorCode] 128.14 KB (🟡 +59 B) 238.58 KB
Details

Only the gzipped size is provided here based on an expert tip.

First Load is the size of the global bundle plus the bundle for the individual page. If a user were to show up to your website and land on a given page, the first load size represents the amount of javascript that user would need to download. If next/link is used, subsequent page loads would only need to download that page's bundle (the number in the "Size" column), since the global bundle has already been downloaded.

Any third party scripts you have added directly to your app using the <script> tag are not accounted for in this analysis

Next to the size is how much the size has increased or decreased compared with the base branch of this PR. If this percentage has increased by 10% or more, there will be a red status indicator applied, indicating that special attention should be given to this.

@@ -12,6 +12,12 @@ const cachedValue = useMemo(calculateValue, dependencies)

</Intro>

<Note>

[React Compiler](/learn/react-compiler) automatically memoizes values and functions, reducing the need for manual `useMemo` calls. You can use the compiler to handle memoization automatically.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is great, was just browsing the docs and surprised it wasn't there

import { isCompilerEnabled } from 'ReactCompilerFeatureFlag';
import { c as _c } from 'react/compiler-runtime';

const MyComponent = isCompilerEnabled()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If that's implemented at a component level and doesn't utilize dynamic imports, can we expect a big increase in bundle size?

As effectively, we are importing double the amount of code per optimized component?

I know it won't be exactly a 2x increase, as we aren't optimizing node_modules nor normal functions, but I wonder if this would negatively impact production in a significant way.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah the number of lines per function does get doubled, but the bundle size increase isn't 1:1 with the number of lines since it depends on the compression algorithm. For us, we found that a React Native (Android) project saw an overall increase of ~10% in apk size.

There's some tradeoff math here to do, while bundle size may increase in the gating mode, you will likely see performance benefits in the test case that tradeoff against the bundle size. You'll need to run an experiment to really be sure.

Of course the gating mode is optional too. The Incremental Adoption page covers a bit more about other strategies.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it, thanks @poteto!


One of the main ways React Compiler can break your app is if your code was written to rely on memoization for correctness. This means your app depends on specific values being memoized to work properly. Since the compiler may memoize differently than your manual approach, this can lead to unexpected behavior like effects over-firing, infinite loops, or missing updates.

Common scenarios where this occurs:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another breaking pattern is when you reference a variable declaration inside a callback that was declared after the callback. This can happen accidentally when writing an especially complex component or hook. That callback won't be memoized by the compiler.

Would you consider this a common scenario?
https://playground.react.dev/#N4Igzg9grgTgxgUxALhAMygOzgFwJYSYAEAQgIYAmAFAJRHAA6xRchYORhAwgDZ5wBrIgF4itEQD56TIrJZsIPBADoeEAOZU0ECDRlEAvvv2tM7IgG1tEADREwCHADEdAXRFEoDgMo4yOBCoABj1MfRhHWGIAHgo8ADdOTF5+AWFgbj5BAwlgawNogHo4+IkmIzDMDGx8QiIAcR1qOkZmU3MrHTsHZzcPLwRff0CQkzYOTNSPcWEpVrl5M0UVNU1rUNkK8MiYGJKklMF0yezc-KKSsswDEAMgA

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah this is a fun case. Note that you can't memoize such callbacks yourself — adding a useCallback() in the "Bad" example in your playground link would be a TDZ violation when you added the dep on foo:

function Bad() {
  const onClick = useCallback(() => {
    console.log(foo)
  }, [foo /* TDZ violation */ ]);
  
  const [foo, setFoo] = useState(0)

  return <div onClick={onClick}>{foo}</div>
}

We can look into adding linting against this pattern to suggest moving the callback. Note that the compiler will already reject this code if you try to manually memoize it, because the dependency changes later.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you consider this a common scenario?

Since this pattern doesn't work with manual memoization, this pattern is most likely in code that isn't memoized already. Definitely worth adding validation to help identify opportunities in such code to memoize more.

plugins: [
['babel-plugin-react-compiler', {
compilationMode: 'annotation', // Only compile "use memo" components
panicThreshold: 'NONE' // More permissive for experimental code
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
panicThreshold: 'NONE' // More permissive for experimental code
panicThreshold: 'none' // More permissive for experimental code

lowercased to match the types and be more consistent with src/content/reference/react-compiler/panicThreshold.md

test: './src/production/**/*.{js,jsx,ts,tsx}',
plugins: [
['babel-plugin-react-compiler', {
panicThreshold: 'CRITICAL_ERRORS' // Stricter for production code
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
panicThreshold: 'CRITICAL_ERRORS' // Stricter for production code
panicThreshold: 'critical_errors' // Stricter for production code

We've received [feedback](https://bsky.app/profile/danabra.mov/post/3lr46ciujjs2r) that the compiler docs are difficult to understand and not prominent enough that people don't realize the compiler is a serious project and is near stable.

This PR rewrites the whole compiler doc section, giving it its own category as well as a standalone reference page.

Preview: https://react-dev-git-pr7868-fbopensource.vercel.app/

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ Question:

It may not belong to the "incremental adoption" section, but I wonder if it's worth answering the following question in the adoption plan or somewhere else:

There is a recurring question regarding the best practice for handling existing manual memoization APIs (useMemo, useCallback, memo) in a codebase after the React Compiler has been enabled. This uncertainty is common in discussions about adopting the compiler for large, legacy applications.

To address this, we could add a section to the official documentation, that answers the following:

FAQ: What is the recommended strategy for useMemo, useCallback, and memo in a large and legacy codebase after enabling the React Compiler?

Once the compiler is active, should teams begin a gradual process of removing these manual memoizations?
Our working assumption is that this is the intended outcome, as it aligns with the core purpose of the compiler.

Providing clear, official guidance on this topic will significantly reduce community uncertainty and help teams establish a consistent and effective adoption plan.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ Question:

Is the use of react-compiler-healthcheck recommended at all?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants